﻿#region The MIT License
// The MIT License
// 
// Copyright (c) 2009 octalforty studios
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#endregion
using System.Linq;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;

using Irony.Parsing;

using octalforty.Chinchillin.Console.Compiler.SemanticModel;
using octalforty.Chinchillin.Shared.Deployment;

namespace octalforty.Chinchillin.Console.Compiler
{
    public class CdmCompiler
    {
        private IPlaceholderResolver placeholderResolver;
        private readonly Regex R = new Regex(@"\$\((?<PlaceholderName>.+?)\)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);
        
        public CdmCompiler()
        {
        }

        public CdmCompiler(IPlaceholderResolver placeholderResolver)
        {
            this.placeholderResolver = placeholderResolver;
        }

        public DeploymentManifest Compile(string source)
        {
            var grammar = new CdmGrammar();
            var parser = new Parser(grammar);
            var parseTree = parser.Parse(source);

            if(parseTree.HasErrors())
            {
                throw new CdmCompilationException("Couldn't compile source",
                    parseTree.ParserMessages.
                        ConvertAll(pm => string.Format("{0} at ({1})", pm.Message, pm.Location)).
                        ToArray());
            } // if

            return ToDeploymentManifest(parseTree);
        }

        private DeploymentManifest ToDeploymentManifest(ParseTree parseTree)
        {
            var servers = new List<ServerDefinition>();
            foreach(var serverDefinitionNode in parseTree.Root.GetNodes("serverDefinition"))
            {
                var name = Resolve(GetStringPropertyValue(serverDefinitionNode, "name"));
                var address = Resolve(GetStringPropertyValue(serverDefinitionNode, "address"));
                var login = Resolve(GetStringPropertyValue(serverDefinitionNode, "login"));
                var password = Resolve(GetStringPropertyValue(serverDefinitionNode, "password"));
                
                var serverDefinition = new ServerDefinition(name, address, new NetworkCredential(login, password));

                servers.Add(serverDefinition);
            } // foreach

            var tasks = new List<TaskDefinition>();
            foreach(var taskDefinitionNode in parseTree.Root.GetNodes("taskDefinition"))
            {
                var name = GetStringPropertyValue(taskDefinitionNode, "name");
                var dependsOn = GetListOfStringPropertyValue(taskDefinitionNode, "depends-on");

                var commitActions = new List<ActionExecution>();
                foreach(var actionExecutionNode in taskDefinitionNode.GetNode("commit").GetNode("execs").GetImmediateNodes("exec"))
                {
                    var serverDesignator = (string)actionExecutionNode.ChildNodes[1].Token.Value;
                    var actionName = (string)actionExecutionNode.ChildNodes[3].Token.Value;

                    var commitAction = new ActionExecution(serverDesignator, actionName, GetStringPropertyValues(actionExecutionNode).ToDictionary(k => k[0], k => Resolve(k[1])));
                    commitActions.Add(commitAction);
                } // foreach

                var rollbackActions = new List<ActionExecution>();
                if(taskDefinitionNode.GetNode("rollback") != null && taskDefinitionNode.GetNode("rollback").GetNode("execs") != null)
                    foreach(var actionExecutionNode in taskDefinitionNode.GetNode("rollback").GetNode("execs").GetImmediateNodes("exec"))
                    {
                        var serverDesignator = (string)actionExecutionNode.ChildNodes[1].Token.Value;
                        var actionName = (string)actionExecutionNode.ChildNodes[3].Token.Value;

                        var rollbackAction = new ActionExecution(serverDesignator, actionName, GetStringPropertyValues(actionExecutionNode).ToDictionary(k => k[0], k => Resolve(k[1])));
                        rollbackActions.Add(rollbackAction);
                    } // foreach

                var taskDefinition = new TaskDefinition(name, new List<string>(dependsOn ?? new string[] { }), new List<ActionExecution>(commitActions), new List<ActionExecution>(rollbackActions));
                tasks.Add(taskDefinition);
            } // foreach

            var manifest = new DeploymentManifest(servers, tasks);
            return manifest;
        }

        private string[] GetListOfStringPropertyValue(ParseTreeNode parseTreeNode, string propertyName)
        {
            var propertyAssignments = parseTreeNode.GetNodes("propertyAssignment");

            foreach(var pa in propertyAssignments)
            {
                if(pa.ChildNodes[0].Token.Text == propertyName)
                {
                    var termList = pa.ChildNodes[2].GetNode("termList");
                    return termList.ChildNodes.
                        Select(n => Resolve((string)n.ChildNodes[0].Token.Value)).
                        ToArray();
                } // if
            } // foreach

            return null;
        }

        private string GetStringPropertyValue(ParseTreeNode parseTreeNode, string propertyName)
        {
            var propertyAssignments = parseTreeNode.GetNodes("propertyAssignment");

            foreach(var pa in propertyAssignments)
            {
                if(pa.ChildNodes[0].Token.Text == propertyName)
                {
                    return (string)pa.ChildNodes[2].ChildNodes[0].Token.Value;
                } // if
            } // foreach

            return null;
        }

        private IEnumerable<string[]> GetStringPropertyValues(ParseTreeNode parseTreeNode)
        {
            var propertyAssignments = parseTreeNode.GetNodes("propertyAssignment");

            foreach(var pa in propertyAssignments)
                yield return new[] { pa.ChildNodes[0].Token.Text, (string)pa.ChildNodes[2].ChildNodes[0].Token.Value };

            yield break;
        }

        private string Resolve(string valueWithPlaceholders)
        {
            if(placeholderResolver == null || string.IsNullOrEmpty(valueWithPlaceholders))
                return valueWithPlaceholders;

            return R.Replace(valueWithPlaceholders, m => placeholderResolver.ResolvePlaceholder(m.Groups["PlaceholderName"].Value));
        }
    }
}
